package cn.itcast.nems.studentrecord.service.impl.studentrecord;

import cn.hutool.core.lang.Assert;
import cn.itcast.ic.common.exception.v2.BizExceptionProducer;
import cn.itcast.ic.common.exception.v2.bean.BizStatusEnum;
import cn.itcast.nems.common.enumeration.NemsErrorBodyEnum;
import cn.itcast.nems.common.utils.WrapperUtil;
import cn.itcast.nems.journal.api.JournalRenewalLineApi;
import cn.itcast.nems.journal.dto.JournalRenewalDTO;
import cn.itcast.nems.journal.dto.JournalRenewalLineDTO;
import cn.itcast.nems.journal.enumeration.JournalRenewalLineStateEnum;
import cn.itcast.nems.journal.enumeration.JournalTransferLineStateEnum;
import cn.itcast.nems.journal.service.*;
import cn.itcast.nems.manager.enrol.EnrolManager;
import cn.itcast.nems.order.api.OrderApi;
import cn.itcast.nems.order.dto.OrderModeLineDTO;
import cn.itcast.nems.studentrecord.api.StudentRecordPaidApi;
import cn.itcast.nems.studentrecord.constant.OrderSourceEnum;
import cn.itcast.nems.studentrecord.constant.ReferenceTypeEnum;
import cn.itcast.nems.studentrecord.converter.StudentRecordFundFlowConverter;
import cn.itcast.nems.studentrecord.converter.StudentRecordJournalConverter;
import cn.itcast.nems.studentrecord.converter.StudentRecordReceivableConverter;
import cn.itcast.nems.studentrecord.dao.entity.*;
import cn.itcast.nems.studentrecord.dto.*;
import cn.itcast.nems.studentrecord.dto.change.StudentRecordJournalDTO;
import cn.itcast.nems.studentrecord.dto.repeat.JournalRepeatDTO;
import cn.itcast.nems.studentrecord.dto.studentrecord.StudentRecordFundFlowDTO;
import cn.itcast.nems.studentrecord.dto.studentrecord.StudentRecordReceivableCreateDTO;
import cn.itcast.nems.studentrecord.enumeration.FundFlowPaymentTypeEnum;
import cn.itcast.nems.studentrecord.enumeration.StudentRecordSourceEnum;
import cn.itcast.nems.studentrecord.service.StudentRecordRepeatService;
import cn.itcast.nems.studentrecord.service.studentrecord.*;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * @author liyong
 * create: 2023-11-17 16:15:54
 **/
@Service
@Slf4j
@RequiredArgsConstructor
public class StudentRecordOrderServiceImpl implements StudentRecordOrderService, StudentRecordPaidApi {
    private final StudentRecordSingupService studentRecordSingupService;
    private final StudentRecordRenewalService studentRecordRenewalService;
    private final StudentRecordTransferService studentRecordTransferService;
    private final StudentRecordRepeatService studentRecordRepeatService;
    private final JournalRenewalService journalRenewalService;
    private final JournalRenewalLineService journalRenewalLineService;
    private final JournalTransferService journalTransferService;
    private final JournalTransferLineService journalTransferLineService;
    private final JournalRepeatService journalRepeatService;
    private final StudentRecordFundFlowService studentRecordFundFlowService;
    private final StudentRecordService studentRecordService;
    private final StudentRecordReceivableService studentRecordReceivableService;
    private final OrderApi orderApi;
    private final JournalRenewalLineApi journalRenewalLineApi;
    private final EnrolManager enrolManager;

    @Override
    @Transactional
    public void orderCanceled(OrderCanceledDTO orderCanceledDTO) {
        final StudentRecord studentRecord = this.studentRecordService.findStudentRecord(orderCanceledDTO.getOrderId());
        if (studentRecord == null) {
            log.warn("订单'{}'取消，没有找到对应的学籍", orderCanceledDTO);
            return;
        }
        final Boolean lockFlag = studentRecord.getLockFlag();
        if (lockFlag != null && lockFlag) {
            log.warn("订单'{}'取消，解锁学籍'{}'", orderCanceledDTO.getOrderId(), studentRecord.getId());
            this.studentRecordService.unlocks(Set.of(studentRecord.getId()), null, null);
        }
        // 升级、重读需要解锁 原班级学籍
        StudentRecordLockDTO lockDTO = this.getJournalOriginStudentRecordIdByOrderId(orderCanceledDTO.getOrderId(), orderCanceledDTO.getSource());
        log.info("【订单取消】升级、重读需要解锁的原班级学籍信息：{}", JSON.toJSONString(lockDTO, SerializerFeature.DisableCircularReferenceDetect));
        if (Objects.nonNull(lockDTO) && Objects.nonNull(lockDTO.getStudentRecordId())) {
            this.studentRecordService.unlocks(Set.of(lockDTO.getStudentRecordId()), null, null);
        }
        final StudentRecordReceivableCreateDTO dto = StudentRecordReceivableConverter.createDTO(studentRecord.getId(), orderCanceledDTO);
        this.studentRecordReceivableService.addStudentRecordReceivable(dto);
        this.studentRecordService.invalidStudentRecord(studentRecord.getId());
        // 改成在订单取消时同步调用
        // final String source = orderCanceledDTO.getSource();
        // if (OrderSourceEnum.NEW.name().contentEquals(source)) {
        // final Integer studentId = this.studentRecordService.getStudentIdByID(studentRecord.getId());
        // log.info("取消报名系统订单, studentId:{}, clazzId:{}", studentId, studentRecord.getClazzId());
        // this.enrolManager.cancelOrder(studentId, studentRecord.getClazzId());
        // }
    }

    /**
     * <p>
     * 功能描述：根据订单ID和订单来源查询凭单行源班级学籍
     * </p>
     *
     * @author zhouwei
     * @since 2024/1/22 7:19 PM
     **/
    private StudentRecordLockDTO getJournalOriginStudentRecordIdByOrderId(String orderId, String orderSource) {
        if (!StringUtils.hasText(orderId) || !StringUtils.hasText(orderSource)) {
            return null;
        }
        // 订单来源：升级｜RENEWAL、跨学科转班｜TRANSFER、重读｜REPEAT
        if (orderSource.equals(OrderSourceEnum.RENEWAL.name())) {
            String journalRenewalLineId = orderApi.getJournalLineIdByOrderId(orderId, OrderSourceEnum.RENEWAL);
            Assert.notEmpty(journalRenewalLineId, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "升级凭单订单关系数据 订单ID：" + orderId));
            JournalRenewalLineDTO renewalLineDTO = journalRenewalLineApi.findById(journalRenewalLineId);
            Assert.notNull(renewalLineDTO, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "升级凭单行ID：" + journalRenewalLineId));
            return new StudentRecordLockDTO(renewalLineDTO.getOriginStudentRecordId(), ReferenceTypeEnum.RENEWAL.name(), renewalLineDTO.getJournalRenewalId());
        } else if (orderSource.equals(OrderSourceEnum.TRANSFER.name())) {
            // 跨学科转班凭单
            String journalTransferLineId = orderApi.getJournalLineIdByOrderId(orderId, OrderSourceEnum.TRANSFER);
            Assert.notEmpty(journalTransferLineId, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "跨学科转班凭单 订单ID：" + orderId));
            JournalTransferLine journalTransferLine = journalTransferLineService.getById(journalTransferLineId);
            Assert.notNull(journalTransferLine, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "转班凭单行ID：" + journalTransferLineId));
            return new StudentRecordLockDTO(journalTransferLine.getOriginStudentRecordId(), ReferenceTypeEnum.TRANSFER.name(), journalTransferLine.getJournalTransferId());
        } else if (orderSource.equals(OrderSourceEnum.REPEAT.name())) {
            // 重读/留级
            JournalRepeat journalRepeat = journalRepeatService.getOne(
                    new WrapperUtil<JournalRepeat>()
                            .limitLaseOne("created_datetime")
                            .eq(JournalRepeat::getOrderId, orderId)
                            .eq(JournalRepeat::getDeletedFlag, false)
            );
            Assert.notNull(journalRepeat, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "重读凭单 订单ID：" + orderId));
            return new StudentRecordLockDTO(journalRepeat.getOriginStudentRecordId(), ReferenceTypeEnum.REPEAT.name(), journalRepeat.getId());
        }
        return null;
    }

    @Override
    @Transactional
    public void orderPaid(OrderPaidDTO orderPaid) {
        log.warn("订单支付: {}", orderPaid);
        StudentRecord studentRecord = this.studentRecordService.findStudentRecord(orderPaid.getOrderId());
        Long studentRecordFundFlowId = this.studentRecordFundFlowService.exsitPaymentNo(studentRecord.getId(), orderPaid.getFlowNo());
        if (studentRecordFundFlowId != null) {
            log.warn("订单支付消息已经处理过，订单ID: {}", orderPaid.getOrderId());
            // 修改学籍流水
            StudentRecordFundFlowDTO studentRecordFundFlow = StudentRecordFundFlowConverter.createStudentRecordFundFlow(orderPaid, studentRecord);
            studentRecordFundFlow.setStudentRecordFundFlowId(studentRecordFundFlowId);
            this.studentRecordFundFlowService.update(studentRecordFundFlow);
            return;
        }
        final StudentRecordSourceEnum source = StudentRecordSourceEnum.valueOf(orderPaid.getSource());
        switch (source) {
            case NEW -> processOrderNew(orderPaid, studentRecord);
            case RENEWAL -> processOrderRenewal(orderPaid, studentRecord);
            case REPEAT -> processOrderRepeat(orderPaid, studentRecord);
            case TRANSFER, EXCHANGE, SUSPENSION, DELAY -> processOrderTransfer(orderPaid, studentRecord);
            case INIT -> addInitStudentRecordFundFlow(orderPaid, studentRecord);
            default -> log.warn("未知的订单源'{}'", orderPaid.getSource());
        }
    }

    @Override
    @Transactional
    public void studentRecordPaid(StudentRecordPaidDTO studentRecordPaid) {
        log.warn("学籍支付: {}", studentRecordPaid);
        final StudentRecord studentRecord = this.studentRecordService.getById(studentRecordPaid.getStudentRecordId());
        final String srSource = studentRecord.getSource();
        Assert.notNull(srSource, BizExceptionProducer.serverError(BizStatusEnum.INTERNAL_SERVER_ERROR.toString(), "学籍ID '%s' 未知的源 '%s'", studentRecord.getId(), studentRecord.getSource()));
        final StudentRecordSourceEnum source = StudentRecordSourceEnum.valueOf(srSource);
        switch (source) {
            case NEW -> processNew(studentRecordPaid);
            case RENEWAL -> processRenewal(studentRecordPaid);
            case REPEAT -> processRepeat(studentRecordPaid);
            case TRANSFER, EXCHANGE, SUSPENSION, DELAY -> processTransfer(studentRecordPaid);
            case INIT -> addInitStudentRecordFundFlow(studentRecordPaid, studentRecord);
            default -> log.warn("未知的学籍源'{}'", studentRecordPaid);
        }
    }

    private void processOrderTransfer(OrderPaidDTO orderPaid, StudentRecord studentRecord) {
        final String journalLineId = this.journalTransferLineService.findLineIdByOrderId(orderPaid.getOrderId());
        final JournalTransferLine line = this.journalTransferLineService.getById(journalLineId);
        final JournalTransfer journal = this.journalTransferService.getById(line.getJournalTransferId());
        StudentRecordJournalDTO studentRecordJournal = StudentRecordJournalConverter.orderTransfer(orderPaid, journal, line);
        studentRecordJournal.setTargetStudentRecordId(studentRecord.getId());
        this.studentRecordTransferService.transferComplete(studentRecordJournal);

        final StudentRecordFundFlowDTO fundFlow = StudentRecordFundFlowConverter.createStudentRecordFundFlow(orderPaid, studentRecord);
        this.updateJournalTransferState(journalLineId, fundFlow);
    }

    private void processTransfer(StudentRecordPaidDTO studentRecordPaid) {
        final String journalLineId = this.journalTransferLineService.findLineIdByJobStudentRecordId(studentRecordPaid.getStudentRecordId());
        final JournalTransferLine line = this.journalTransferLineService.getById(journalLineId);
        final JournalTransfer journal = this.journalTransferService.getById(line.getJournalTransferId());
        StudentRecordJournalDTO studentRecordJournal = StudentRecordJournalConverter.transfer(journal, line);
        studentRecordJournal.setTargetStudentRecordId(studentRecordPaid.getStudentRecordId());
        this.studentRecordTransferService.transferComplete(studentRecordJournal);

        final StudentRecordFundFlowDTO fundFlow = StudentRecordFundFlowConverter.createStudentRecordFundFlow(studentRecordPaid, studentRecordJournal, studentRecordJournal.getJournalType());
        this.updateJournalTransferState(journalLineId, fundFlow);
    }

    private void updateJournalTransferState(String journalLineId, StudentRecordFundFlowDTO fundFlow) {
        fundFlow.setUpdateStudentRecord(true);
        Boolean isPayComplete = this.studentRecordFundFlowService.create(fundFlow);
        if (isPayComplete != null) {
            JournalTransferLineStateEnum state = isPayComplete ? JournalTransferLineStateEnum.ORDER_PAID : JournalTransferLineStateEnum.ORDER_PAYING;
            TransferOrderStateUpdateParams params = new TransferOrderStateUpdateParams(state, journalLineId);
            this.journalTransferService.onTransferOrderStateUpdate(params);
        }
    }

    private void processOrderRepeat(OrderPaidDTO orderPaid, StudentRecord studentRecord) {
        final JournalRepeatDTO journalRepeatDTO = this.journalRepeatService.findByTargetStudentRecordId(studentRecord.getId());
        StudentRecordJournalDTO studentRecordJournal = StudentRecordJournalConverter.orderRepeat(orderPaid, journalRepeatDTO);
        this.studentRecordRepeatService.repeatComplete(studentRecordJournal);

        final StudentRecordFundFlowDTO fundFlow = StudentRecordFundFlowConverter.createStudentRecordFundFlow(orderPaid, studentRecord);
        fundFlow.setUpdateStudentRecord(true);
        this.studentRecordFundFlowService.create(fundFlow);
    }

    private void processRepeat(StudentRecordPaidDTO studentRecordPaid) {
        final JournalRepeatDTO journalRepeatDTO = this.journalRepeatService.findByTargetStudentRecordId(studentRecordPaid.getStudentRecordId());
        StudentRecordJournalDTO studentRecordJournal = StudentRecordJournalConverter.repeat(journalRepeatDTO);
        this.studentRecordRepeatService.repeatComplete(studentRecordJournal);

        final StudentRecordFundFlowDTO fundFlow = StudentRecordFundFlowConverter.createStudentRecordFundFlow(studentRecordPaid, studentRecordJournal, ReferenceTypeEnum.REPEAT);
        fundFlow.setUpdateStudentRecord(true);
        this.studentRecordFundFlowService.create(fundFlow);
    }

    private void processOrderRenewal(OrderPaidDTO orderPaid, StudentRecord studentRecord) {
        final String journalRenewalLineId = this.journalRenewalLineService.findLineIdByOrderId(orderPaid.getOrderId());
        final JournalRenewalLine journalRenewalLine = this.journalRenewalLineService.getById(journalRenewalLineId);
        final JournalRenewalDTO journalRenewalDTO = this.journalRenewalService.findById(journalRenewalLine.getJournalRenewalId());
        StudentRecordJournalDTO studentRecordJournal = StudentRecordJournalConverter.orderRenewal(orderPaid, journalRenewalDTO, journalRenewalLine);
        studentRecordJournal.setTargetStudentRecordId(studentRecord.getId());
        this.studentRecordRenewalService.renewalComplete(studentRecordJournal);

        final StudentRecordFundFlowDTO fundFlow = StudentRecordFundFlowConverter.createStudentRecordFundFlow(orderPaid, studentRecord);
        this.addFlowUpateJournalRenewalState(journalRenewalLineId, fundFlow);
    }

    private void processRenewal(StudentRecordPaidDTO studentRecordPaid) {
        final String journalRenewalLineId = this.journalRenewalLineService.findLineIdByJobStudentRecordId(studentRecordPaid.getStudentRecordId());
        final JournalRenewalLine journalRenewalLine = this.journalRenewalLineService.getById(journalRenewalLineId);
        final JournalRenewalDTO journalRenewalDTO = this.journalRenewalService.findById(journalRenewalLine.getJournalRenewalId());
        StudentRecordJournalDTO studentRecordJournal = StudentRecordJournalConverter.renewal(journalRenewalDTO, journalRenewalLine);
        studentRecordJournal.setTargetStudentRecordId(studentRecordPaid.getStudentRecordId());
        this.studentRecordRenewalService.renewalComplete(studentRecordJournal);

        final StudentRecordFundFlowDTO fundFlow = StudentRecordFundFlowConverter.createStudentRecordFundFlow(studentRecordPaid, studentRecordJournal, ReferenceTypeEnum.RENEWAL);
        this.addFlowUpateJournalRenewalState(journalRenewalLineId, fundFlow);
    }

    private void addFlowUpateJournalRenewalState(String journalRenewalLineId, final StudentRecordFundFlowDTO fundFlow) {
        fundFlow.setUpdateStudentRecord(true);
        Boolean isPayComplete = this.studentRecordFundFlowService.create(fundFlow);
        if (isPayComplete != null) {
            JournalRenewalLineStateEnum state = isPayComplete ? JournalRenewalLineStateEnum.ORDER_PAID : JournalRenewalLineStateEnum.ORDER_PAYING;
            this.journalRenewalLineService.updateState(journalRenewalLineId, state);
        }
    }

    private void processOrderNew(OrderPaidDTO orderPaid, StudentRecord studentRecord) {
        StudentRecordJournalDTO studentRecordJournal = StudentRecordJournalConverter.orderSingup(studentRecord, orderPaid);
        studentRecordJournal.setTargetStudentRecordId(studentRecord.getId());
        studentRecordSingupService.singupComplete(studentRecordJournal);
        StudentRecordFundFlowDTO fundFlow = new StudentRecordFundFlowDTO();
        cn.itcast.nems.order.dto.StudentRecordFundFlowDTO studentRecordFundFlowDTO = orderPaid.getStudentRecordFundFlowDTO();
        if (studentRecordFundFlowDTO != null) {
            BeanUtils.copyProperties(studentRecordFundFlowDTO, fundFlow);
            studentRecordFundFlowDTO.setReferenceId(orderPaid.getOrderId());
            final String orderLineId = orderPaid.getOrderLineIds().get(0);
            studentRecordFundFlowDTO.setReferenceLineId(orderLineId);
            if (studentRecordFundFlowDTO.getPaymentType() != null) {
                fundFlow.setPaymentType(FundFlowPaymentTypeEnum.valueOf(studentRecordFundFlowDTO.getPaymentType()));
            }
            if (studentRecordFundFlowDTO.getReferenceType() != null) {
                fundFlow.setReferenceType(studentRecordFundFlowDTO.getReferenceType());
            }
        } else {
            fundFlow = StudentRecordFundFlowConverter.createStudentRecordFundFlow(orderPaid, studentRecord);
        }
        fundFlow.setUpdateStudentRecord(true);
        this.studentRecordFundFlowService.create(fundFlow);
    }

    private void processNew(StudentRecordPaidDTO studentRecordPaid) {
        final String orderId = this.studentRecordService.findOrderId(studentRecordPaid.getStudentRecordId());
        StudentRecord studentRecord = this.studentRecordService.getById(studentRecordPaid.getStudentRecordId());
        StudentRecordJournalDTO studentRecordJournal = StudentRecordJournalConverter.singup(studentRecord, orderId);
        final List<OrderModeLineDTO> lines = this.orderApi.findOrderLines(orderId);
        final String lineId = lines.get(0).getId();
        studentRecordJournal.setJournalLineId(lineId);
        studentRecordJournal.setTargetStudentRecordId(studentRecord.getId());
        studentRecordSingupService.singupComplete(studentRecordJournal);

        final StudentRecordFundFlowDTO fundFlow = StudentRecordFundFlowConverter.createStudentRecordFundFlow(studentRecordPaid, studentRecord);
        fundFlow.setUpdateStudentRecord(true);
        fundFlow.setReferenceType(ReferenceTypeEnum.ORDER);
        fundFlow.setReferenceLineId(orderId);
        fundFlow.setReferenceLineId(lineId);
        this.studentRecordFundFlowService.create(fundFlow);
    }

    private void addInitStudentRecordFundFlow(OrderPaidDTO orderPaid, StudentRecord studentRecord) {
        final StudentRecordFundFlowDTO fundFlow = StudentRecordFundFlowConverter.createStudentRecordFundFlow(orderPaid, studentRecord);
        fundFlow.setUpdateStudentRecord(true);
        this.studentRecordFundFlowService.create(fundFlow);
    }

    private void addInitStudentRecordFundFlow(StudentRecordPaidDTO studentRecordPaid, StudentRecord studentRecord) {
        final StudentRecordFundFlowDTO fundFlow = StudentRecordFundFlowConverter.createInitStudentRecordFundFlow(studentRecordPaid, studentRecord);
        fundFlow.setUpdateStudentRecord(true);
        this.studentRecordFundFlowService.create(fundFlow);
    }

    private void addStudentRecordFundFlow(StudentRecordPaidDTO studentRecordPaid, StudentRecord studentRecord, ReferenceTypeEnum referenceType) {
        final StudentRecordFundFlowDTO fundFlow = StudentRecordFundFlowConverter.createStudentRecordFundFlow(studentRecordPaid, studentRecord, referenceType);
        fundFlow.setUpdateStudentRecord(true);
        this.studentRecordFundFlowService.create(fundFlow);
    }
}
